﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
using Bling.Angles;

namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.DX;

  public class Parallex : HasHost {
    private static readonly GetProperty<Parallex, Double3Bl> EyePositionProperty =
      "EyePosition".NewProperty<Parallex, Double3Bl>(new Double3Bl(0, 0, -1));

    public Double3Bl Eye { get; set; }

    public Parallex() : base(false) {
      var Uri = new Uri[3];
      Uri[0] = "Resources/Stone-diffuse.jpg".MakePackUri(typeof(Bump));
      Uri[1] = "Resources/Stone-normal.jpg".MakePackUri(typeof(Bump));
      Uri[2] = "Resources/Stone-bump.jpg".MakePackUri(typeof(Bump));

      var changeMaterial = new ComboBoxBl(this) { LeftTop = new Double2Bl(550, 0), SelectedIndex = 0 };
      changeMaterial.Items.Add(new ComboBoxItem() { Content = "Brick" });

      Host.Size = 550;
      var sliderX = new LabelSliderBl(this) {
        LabelName = "X",
        ZIndex = 100,
      };
      var sliderY = new LabelSliderBl(this) {
        LabelName = "Y",
        ZIndex = 100,
        LeftTop = sliderX.LeftBottom,
        Value = 0.5
      };
      var sliderZ = new LabelSliderBl(this) {
        LabelName = "Z",
        ZIndex = 100,
        LeftTop = sliderY.LeftBottom,
        Minimum = -4, Maximum = -0.25, Value = -1
      };
      var sliderS = new LabelSliderBl(this) {
        LabelName = "Texture Scale",
        ZIndex = 100,
        LeftTop = sliderZ.LeftBottom,
        Minimum = 1, Maximum = 6, Value = 1
      };
      var sliderH = new LabelSliderBl(this) {
        LabelName = "Height Scale",
        ZIndex = 100,
        LeftTop = sliderS.LeftBottom,
        Minimum = 0, Maximum = 0.04, Value = 0.02
      };
      Matrix4Bl world = Matrix4Bl.Identity; //sliderX.Value.PI2().RotateAround(new Point3DBl(1d, 1d, sliderZ)).Matrix;
      //world = world * sliderY.Value.PI2().RotateAround(new Point3DBl(sliderZ, 1d, 1d));

      Eye = new Double3Bl(sliderX.Value - 0.5, sliderY.Value - 0.5, sliderZ.Value);
      var at = new Double3Bl(0f, 0f, 0f);
      var up = new Double3Bl(0f, 1f, 0f);
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = Eye,
        Direction = { Look = at, Up = up },
        FieldOfView = .25.PI().Degrees,
        PlaneDistance = { Far = 10d },
      };

      //var view = Eye.LookAt(at, up);
      //var project = (.25.PI()).PerspectiveFov(550d / 550d, .1, 10d);
      var ambLight = new AmbientLightBl() { Color = Colors.White.Bl() };

      var diffMaterial = new DiffuseMaterialCl() { Ambient = { Factor = 0.3 } };
      var specMaterial = new SpecularMaterialCl() { Power = 1, Factor = 0.2 };

      var Surface = new PSurface(p => new Double3Bl(p, 0d)); //GraphicDefinitions.Sphere.Displace((GraphicDefinitions.EggCrate.Adjust(5d, .05d, 0)));
      var SurfaceKeys = new PSurfaceSample(200, 200);

      // easier...

      TextureBl Texture = new ImageTexture<D2>(Uri[0]);
      TextureBl Normal = new ImageTexture<D2>(Uri[1]);
      TextureBl Height = new ImageTexture<D2>(Uri[2]);

      //convert all dir\position to tangent space
      Device.Render(SurfaceKeys, (n, uv, vertex) => {
        vertex.Position = (Camera.Project * Camera.View * world * Surface)[uv]; ;
        var usePosition = world.Transform(Surface[uv]);

        //compute the inverse matrix of TBN ,which transform from object space to tangent space,
        //as TBN is orthogonal matrix, use its transport to get its inverse
        var N = world.Transform(Surface.Normals[uv]).Normalize;
        var T = world.Transform(Surface.Tangents[uv]).Normalize;
        var B = N.Cross(T).Normalize;
        Matrix3Bl TBN = Matrix3Bl.ByRows(T, B, N);
        Matrix3Bl TBNinverse = TBN.Inverse; 

        var viewDir = ((Eye - usePosition) * TBNinverse).Normalize;
        //different parameter surface seems need different way to load texture
        var uv0 = new Double2Bl(uv.X + 0.5, uv.Y + 0.5) * sliderS.Value % 1;
        //use eyeposition and height infomation to implement parallex mapping
        var uv1 = uv0 + viewDir.XY() * Height[uv0].X * sliderH.Value;
        var norm = (Double3Bl)(Normal[uv1].XYZ() * 2 - 1).Normalize;

        //the normals and bias coming from parallex mapping is in tangent space, so convert all dir\position
        //of light and eye from object space to tangent space
        var dirLight = new SpotLightBl() {
          Direction = new Double3Bl(0, 0, -1) *  TBNinverse,
          Color = Colors.White,
          Position = new Double3Bl(0, 0, 2) * TBNinverse,
          InnerConeAngle = 4.Degrees(),
          OuterConeAngle = 8.Degrees(),
        };

        Lighting L = (diffMaterial + specMaterial).ApplyLights(dirLight + ambLight);

        vertex.Color = L.ApplyAt(usePosition * TBNinverse, norm, -Eye * TBNinverse).ForPixel(Texture, uv1);
      });

      sliderX.LeftTop = Host.LeftBottom;
    }
  }
}
